# -*- coding:binary -*-
require 'spec_helper'

require 'msf/core'
require 'msf/core/exploit/smb/server/share'
require 'rex/proto/smb/constants'

RSpec.describe Msf::Exploit::Remote::SMB::Server::Share do

  include_context "Msf::StringIO"

  subject(:mod) do
    mod = Msf::Exploit.new
    mod.extend described_class
    mod.send(:initialize)

    mod
  end

  let(:default_info_basic_res_length) { 101 }
  let(:default_info_basic_res) do
    "\x00\x00\x00\x61\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" +
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
    "\x00\x00\x44\x43\x0a\x02\x00\x28\x00\x00\x00\x02\x00\x37\x00\x00" +
    "\x00\x28\x00\x39\x00\x00\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x00" +
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
    "\x00\x00\x00\x00\x00"
  end

  let(:default_info_standard_res_length) { 83 }
  let(:default_info_standard_res) do
    "\x00\x00\x00\x4f\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" +
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
    "\x00\x00\x44\x43\x0a\x02\x00\x16\x00\x00\x00\x02\x00\x37\x00\x00" +
    "\x00\x16\x00\x39\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00" +
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
    "\x00\x00\x00"
  end

  let(:default_info_network_res_length) { 117 }
  let(:default_info_network_res) do
    "\x00\x00\x00\x71\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" +
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" +
    "\x00\x00\x44\x43\x0a\x02\x00\x38\x00\x00\x00\x02\x00\x37\x00\x00" +
    "\x00\x38\x00\x39\x00\x00\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x00" +
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
    "\x00\x00\x00\x00\x00"
  end

  let(:non_existent_fid) { 0x1234 }
  let(:file_fid) { 0xdead }
  let(:folder_fid) { 0xbeef }

  let(:non_existent_path) { 'non_existent' }
  let(:file_path) { 'test.exe' }
  let(:folder_path) { '\\' }

  let(:error_res_length) { 39 }

  let(:existent_fid_info_basic_res_length) { 101 }
  let(:info_standard_res_length) { 83 }
  let(:existent_path_info_basic_res_length) { 101 }
  let(:existent_path_info_standard_res_length) { 83 }
  let(:existent_path_info_network_res_length) { 117 }

  before(:example) do
    msf_io.string = ''
    mod.instance_variable_set('@state', {
      msf_io => {
        :multiplex_id => 0x41424344,
        :process_id   => 0x45464748,
        :file_id      => 0xdead,
        :dir_id       => 0xbeef
      }
    })
    mod.lo = 0
    mod.hi = 0
    mod.share = 'test'
    mod.file_name = 'test.exe'
    mod.file_contents = 'metasploit'
  end

  describe "#send_info_basic_res" do
    context "when no opts" do
      it "returns the number of bytes sent" do
        expect(mod.send_info_basic_res(msf_io)).to eq(default_info_basic_res_length)
      end

      it "sends a default TRANSACTION2 response" do
        mod.send_info_basic_res(msf_io)
        res = msf_io.read
        expect(res).to eq(default_info_basic_res)
      end
    end
  end

  describe "#send_info_standard_res" do
    context "when no opts" do
      it "returns the number of bytes sent" do
        expect(mod.send_info_standard_res(msf_io)).to eq(default_info_standard_res_length)
      end

      it "sends a default TRANSACTION2 response" do
        mod.send_info_standard_res(msf_io)
        res = msf_io.read
        expect(res).to eq(default_info_standard_res)
      end
    end
  end

  describe "#send_info_network_res" do
    context "when no opts" do
      it "returns the number of bytes sent" do
        expect(mod.send_info_network_res(msf_io)).to eq(default_info_network_res_length)
      end

      it "sends a default TRANSACTION2 response" do
        mod.send_info_network_res(msf_io)
        res = msf_io.read
        expect(res).to eq(default_info_network_res)
      end
    end
  end

  describe "#smb_cmd_trans_query_file_info_basic" do
    context "when non existent fid" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_file_info_basic(msf_io, non_existent_fid)).to eq(error_res_length)
      end

      it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do
        mod.smb_cmd_trans_query_file_info_basic(msf_io, non_existent_fid)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)

        expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND)
      end
    end

    context "when existent file fid" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_file_info_basic(msf_io, file_fid)).to eq(existent_fid_info_basic_res_length)
      end

      it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_NORMAL ExtFileAttributes" do
        mod.smb_cmd_trans_query_file_info_basic(msf_io, file_fid)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)
        param_count = trans2_res['Payload'].v['ParamCount']
        data_count = trans2_res['Payload'].v['DataCount']

        data  = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
        smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct
        smb_data.from_s(data)

        expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_NORMAL)
      end
    end

    context "when existent folder fid" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_file_info_basic(msf_io, folder_fid)).to eq(existent_fid_info_basic_res_length)
      end

      it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_DIRECTORY ExtFileAttributes" do
        mod.smb_cmd_trans_query_file_info_basic(msf_io, folder_fid)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)
        param_count = trans2_res['Payload'].v['ParamCount']
        data_count = trans2_res['Payload'].v['DataCount']

        data  = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
        smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct
        smb_data.from_s(data)

        expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_DIRECTORY)
      end
    end
  end

  describe "#smb_cmd_trans_query_file_info_standard" do
    it "returns the number of bytes sent" do
      expect(mod.smb_cmd_trans_query_file_info_standard(msf_io, non_existent_fid)).to eq(info_standard_res_length)
    end

    it "always sends a TRANSACTION2 response with SMB_QUERY_FILE_STANDARD_INFO about the shared file" do
      mod.smb_cmd_trans_query_file_info_standard(msf_io, non_existent_fid)
      res = msf_io.read

      trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
      trans2_res.from_s(res)
      param_count = trans2_res['Payload'].v['ParamCount']
      data_count = trans2_res['Payload'].v['DataCount']

      data  = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
      smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct
      smb_data.from_s(data)

      expect(smb_data.v['EndOfFile']).to eq(mod.file_contents.length)
    end
  end

  describe "#smb_cmd_trans_query_path_info_basic" do
    context "when non existent path" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_path_info_basic(msf_io, non_existent_path)).to eq(error_res_length)
      end

      it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do
        mod.smb_cmd_trans_query_path_info_basic(msf_io, non_existent_path)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)

        expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND)
      end
    end

    context "when existent file path" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_path_info_basic(msf_io, file_path)).to eq(existent_path_info_basic_res_length)
      end

      it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_NORMAL ExtFileAttributes" do
        mod.smb_cmd_trans_query_path_info_basic(msf_io, file_path)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)
        param_count = trans2_res['Payload'].v['ParamCount']
        data_count = trans2_res['Payload'].v['DataCount']

        data  = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
        smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct
        smb_data.from_s(data)

        expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_NORMAL)
      end
    end

    context "when existent folder path" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_path_info_basic(msf_io, folder_path)).to eq(existent_path_info_basic_res_length)
      end

      it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_DIRECTORY ExtFileAttributes" do
        mod.smb_cmd_trans_query_path_info_basic(msf_io, folder_path)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)
        param_count = trans2_res['Payload'].v['ParamCount']
        data_count = trans2_res['Payload'].v['DataCount']

        data  = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
        smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct
        smb_data.from_s(data)

        expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_DIRECTORY)
      end
    end
  end

  describe "#smb_cmd_trans_query_path_info_standard" do
    context "when non existent path" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_path_info_standard(msf_io, non_existent_path)).to eq(error_res_length)
      end

      it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do
        mod.smb_cmd_trans_query_path_info_standard(msf_io, non_existent_path)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)

        expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND)
      end
    end

    context "when existent file path" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_path_info_standard(msf_io, file_path)).to eq(existent_path_info_standard_res_length)
      end

      it "sends a TRANSACTION2 response with the Directory field unset" do
        mod.smb_cmd_trans_query_path_info_standard(msf_io, file_path)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)
        param_count = trans2_res['Payload'].v['ParamCount']
        data_count = trans2_res['Payload'].v['DataCount']

        data  = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
        smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct
        smb_data.from_s(data)

        expect(smb_data.v['Directory']).to eq(0)
      end
    end

    context "when existent folder path" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_path_info_standard(msf_io, folder_path)).to eq(existent_path_info_standard_res_length)
      end

      it "sends a TRANSACTION2 response with the Directory field set" do
        mod.smb_cmd_trans_query_path_info_standard(msf_io, folder_path)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)
        param_count = trans2_res['Payload'].v['ParamCount']
        data_count = trans2_res['Payload'].v['DataCount']

        data  = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
        smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct
        smb_data.from_s(data)

        expect(smb_data.v['Directory']).to eq(1)
      end
    end
  end


  describe "#smb_cmd_trans_query_path_info_network" do
    context "when non existent path" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_path_info_network(msf_io, non_existent_path)).to eq(error_res_length)
      end

      it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do
        mod.smb_cmd_trans_query_path_info_network(msf_io, non_existent_path)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)

        expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND)
      end
    end

    context "when existent file path" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_path_info_network(msf_io, file_path)).to eq(existent_path_info_network_res_length)
      end

      it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_NORMAL ExtFileAttributes" do
        mod.smb_cmd_trans_query_path_info_network(msf_io, file_path)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)
        param_count = trans2_res['Payload'].v['ParamCount']
        data_count = trans2_res['Payload'].v['DataCount']

        data  = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
        smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_NETWORK_INFO_HDR.make_struct
        smb_data.from_s(data)

        expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_NORMAL)
      end
    end

    context "when existent folder path" do
      it "returns the number of bytes sent" do
        expect(mod.smb_cmd_trans_query_path_info_network(msf_io, folder_path)).to eq(existent_path_info_network_res_length)
      end

      it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_DIRECTORY ExtFileAttributes" do
        mod.smb_cmd_trans_query_path_info_network(msf_io, folder_path)
        res = msf_io.read

        trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct
        trans2_res.from_s(res)
        param_count = trans2_res['Payload'].v['ParamCount']
        data_count = trans2_res['Payload'].v['DataCount']

        data  = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count]
        smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_NETWORK_INFO_HDR.make_struct
        smb_data.from_s(data)

        expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_DIRECTORY)
      end
    end
  end
end


